package com.itstyle.seckill.topic.redis.controller;


import com.itstyle.seckill.topic.redis.Iservice.IRequestAsyncProcessBiz;
import com.itstyle.seckill.topic.redis.Iservice.InventoryProductBiz;
import com.itstyle.seckill.topic.redis.Iservice.Request;
import com.itstyle.seckill.topic.redis.bean.InventoryProduct;
import com.itstyle.seckill.topic.redis.Iservice.serviceImpl.InventoryQueryCacheRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;


/**
 * @Auther: liuwenxiang
 * @Date: 2020/2/23 17:56
 * @Description:
 *
 * redis双写一致性案例
 *  1.查询的时候，将请求加入到队列，然后先查缓存，后查数据库
 *  2.更新的时候，将请求加入到队列
 *  3.然后添加一个监听器，在项目启动的时候执行。
 *      这个监听器，会执行一个init方法，通过这个方法创建一个请求线程池(单例)
 *      在这个请求线程池的构造方法中，创建队列集合。
 *      然后将这个集合中的每个队列交给一个线程去处理。
 *      --处理队列的线程由线程池提供，当队列中有元素时，线程池就会派线程工作。
 */
@RequestMapping("/inventory")
@Controller
public class InventoryQueryCacheController {

    @Autowired
    private InventoryProductBiz inventoryProductBiz;
    @Autowired
    private IRequestAsyncProcessBiz requestAsyncProcessBiz;


    @GetMapping("/test11")
    public void test(){
        InventoryProduct inventoryProduct= inventoryProductBiz.loadInventoryProductByProductId(1);
        System.out.printf("000"+inventoryProduct);
        return ;
    }


    @RequestMapping("/queryInventoryProduct")
    @ResponseBody
    public InventoryProduct queryInventoryProduct(Integer productId) {
        //请求来了，先将请求构建成一个对象，封装查询id，查询接口，是否强制查询的标识
        Request request = new InventoryQueryCacheRequest(productId,inventoryProductBiz,false);
        //根据查询id，hash取模找到一个队列，将请求加入队列
        requestAsyncProcessBiz.process(request);//加入到队列中
        long startTime = System.currentTimeMillis();
        long allTime = 0L;
        long endTime = 0L;
        InventoryProduct inventoryProduct = null;
        //先查缓存，缓存没有，就查数据库
        while (true) {
            //加个延迟防止缓存还未加载
            if (allTime > 200){//如果超过了200ms,那就直接退出，然后从数据库中查询
                break;
            }
            try {
                //从缓存中取数据
                inventoryProduct = inventoryProductBiz.loadInventoryProductCache(productId);
                if (inventoryProduct != null) {
                    return inventoryProduct;
                } else {
                    Thread.sleep(20);//如果查询不到就等20毫秒
                }
                endTime = System.currentTimeMillis();
                allTime = endTime - startTime;
            } catch (Exception e) {
            }
        }
        /**
         * 代码执行到这来，只有以下三种情况
         * 1、缓存中本来有数据，由于redis内存满了，redis通过LRU算法清除了缓存，导致数据没有了
         * 2、由于之前数据库查询比较慢或者内存太小处理不过来队列中的数据，导致队列里挤压了很多的数据，所以一直没有从数据库中获取数据然后插入到缓存中
         * 3、数据库中根本没有这样的数据，这种情况叫数据穿透，一旦别人知道这个商品没有，如果一直执行查询，就会一直查询数据库，如果过多，那么有可能会导致数据库瘫痪
         */
        inventoryProduct = inventoryProductBiz.loadInventoryProductByProductId(productId);
        if (inventoryProduct != null) {
            //构造一个查询请求，放到队列中，异步强制执行-将数据库中的数据添加到缓存
            Request forcRrequest = new InventoryQueryCacheRequest(productId,inventoryProductBiz,true);
            requestAsyncProcessBiz.process(forcRrequest);//这个时候需要强制刷新数据库，使缓存中有数据
            return inventoryProduct;
        }
        return null;

    }
}

